
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <asm/shdev.h>
#include <asm/delay.h>
#endif

#include <uapi/stdlib.h>
#include <uapi/string.h>

#include <system/md5.h>

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include "../lcdi/lcdiapi.h"
#include "../jpeg/jpg2dwj.h"
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include "ff/ff.h"
#include <SDL2/SDL.h>
#else
#include "ff.h"
#endif

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <xwchar.h>
#include <xwcstod.h>
#include <xwstr.h>

///////////////////////////////////////////////////////////////////////////
#define MAX_JPEG_SIZE	0x70000
#define MAX_REST_SIZE	0x10000
#define MAX_BDWJ_SIZE	(MAX_JPEG_SIZE+MAX_REST_SIZE)

#define DO_CHECK_DWJ_MD5

///////////////////////////////////////////////////////////////////////////

typedef enum
{
	FSM_NONE,
	FSM_FXSZ,
	FSM_FULL,
	FSM_PONG,
}FRAME_SHOW_METORD;
#endif

typedef struct
{
	struct SESSION *session;

	session_type_t streamtype;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	int w, h, x, y, iw, ih;

	FRAME_SHOW_METORD methord;
	void *fsm;

	time_t backgroundtms;
	int backgroundcanbechanged;
	uint8_t *load_buf, *dwj_addr, *img_addr;
#endif

	// tmp buffer
	psysbuf_t bufree;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	long load_offset;
	int pixel_w, pixel_h, screen_w, screen_h;
	int eof;
	psysbuf_t load_buf;
	SDL_Window *screen;
	SDL_Renderer* sdlRenderer;
	SDL_Texture* sdlTexture;
	int bpp;
	unsigned char buffer[1280 * 1024 * 32 / 8]; // pixel_w * pixel_h * bpp / 8;
#else
	jpeg_handle_t hpj;

	long vio_out_buf[2]; // double buffer, may not used

	DIBPIXEL backcolor;
	uint32_t framecounter;

	lcdi_jpeg jpeg;
	lcdi_jpeg_view view;
#endif

	long load_size;
	FIL file;
#ifdef MEDIA_AV_SYNC
	session_av_sync *sync;
#endif

} session_unit_t;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
typedef struct
{
	int xs, ys;
	float rs, rmin, rmax;
	int dx, dy;
	float zor, dr;
}fsm_pong_t;

static int decode_fsm_alloc(session_unit_t *punit, FRAME_SHOW_METORD methord)
{
	if (punit == NULL)
		return -EACCES;

	punit->fsm = NULL;
	punit->methord = methord;

	if (punit->methord == FSM_PONG)
	{
		fsm_pong_t *pfsm = (fsm_pong_t *)malloc(sizeof(fsm_pong_t));
		if (pfsm != NULL) {
			memset(pfsm, 0, sizeof(fsm_pong_t));
			pfsm->xs = 1;
			pfsm->ys = 1;
			pfsm->rs = 0.1;
			pfsm->rmin = 0.2;
			pfsm->rmax = 0.8;
		}
		punit->fsm = pfsm;
	}
	else
	{
		punit->fsm = malloc(sizeof(int));
	}

	return 0;

}

static void decode_fsm_free(session_unit_t *punit)
{
	if (punit == NULL)
		return;
	if (punit->fsm != NULL)
		free(punit->fsm);
	punit->fsm = NULL;
	punit->methord = FSM_NONE;
}

static void decode_fsm_erase_background(session_unit_t *punit)
{
	if (punit->methord == FSM_PONG)
	{
		/* clear screen */
		if (punit->jpeg.isize > 0) {
			lcdi_img(&punit->jpeg, &punit->view, 1);
		}
		else
		{
			if ((punit->framecounter % 0x20) == 0) {
				punit->backcolor = random();
			}
			fill_rect(0, 0, lcdi_width(), lcdi_height()
				, GetRValue(punit->backcolor)
				, GetGValue(punit->backcolor)
				, GetBValue(punit->backcolor)
				);
		}
	}
}

// double buffer
static void decode_dbuf_switch(session_unit_t *punit)
{
	dispropty.flags = disp_propty_flags_voutwaitpre;
	dispropty.flags |= disp_propty_flags_dispaddr;
	dispropty.flags |= disp_propty_flags_guiaddr;
	dispropty.flags |= disp_propty_flags_voutaddr;
	dispropty.dispaddr = dispropty.voutaddr;
	if (dispropty.voutaddr == punit->vio_out_buf[0])
		dispropty.voutaddr = punit->vio_out_buf[1];
	else
		dispropty.voutaddr = punit->vio_out_buf[0];
	dispropty.guiaddr = dispropty.voutaddr;
	disp_property(&dispropty);
}

static void decode_fsm_init(session_unit_t *punit)
{
	if (punit == NULL)
		return;

	dispropty.flags = 0;
	disp_property(&dispropty);

	// double buffer
	punit->vio_out_buf[0] = 0x00A00000;
	punit->vio_out_buf[1] = 0x03C00000;
	decode_dbuf_switch(punit);

	debug("\tDISPLAY %d x %d\n", lcdi_width(), lcdi_height());
}

static void decode_fsm_start(session_unit_t *punit)
{
	if (punit == NULL || punit->fsm == NULL)
		return;

	// background
	punit->framecounter = 0;
	punit->backcolor = 0;

	if (punit->methord == FSM_PONG)
	{
		fsm_pong_t *pong = (fsm_pong_t *)punit->fsm;
		pong->zor = 0.5;
		pong->dr = 0.1;
	}

	// switch buffer
	decode_dbuf_switch(punit);

	// double buffer
	decode_fsm_erase_background(punit);
}

static void decode_fsm_stop(session_unit_t *punit)
{
	if (punit == NULL || punit->fsm == NULL)
		return;

	if (punit->methord == FSM_PONG)
	{
		fsm_pong_t *pong = (fsm_pong_t *)punit->fsm;
	}

}

static void decode_fsm_video_changed(session_unit_t *punit, int bestream, void *dat)
{
	uint16_t step, r90 = 0;
	if (punit == NULL || punit->fsm == NULL)
		return;

	dispropty.flags = 0;

	if (bestream) {
		video_stream_info_t *info = (video_stream_info_t *)dat;
		dispropty.flags |= (disp_propty_flags_frame_w | disp_propty_flags_frame_h);
		dispropty.frame_w = info->width;
		dispropty.frame_h = info->height;

	} else {
		psysbuf_t buf = (psysbuf_t)dat;

		// luma, chroma
		dispropty.flags |= disp_propty_flags_vinaddr;
		dispropty.vinaddr = HWADDR((uint32_t)(buf->haddr + buf->offset));

		/* set start */
		dispropty.flags |= disp_propty_flags_voutstart;
	}

	if (dispropty.rotate & ROTATE_VIDEOUT) {
		r90 = !r90;
	}

	// calc output w/h
	if (punit->methord == FSM_FULL)
	{
		punit->iw = (int)(punit->w);
		punit->ih = (int)(punit->h);
	}

	else if (punit->methord == FSM_FXSZ)
	{
		if (r90) {
			punit->iw = dispropty.vout_vd;
			punit->ih = dispropty.vout_hd;
		}
		else {
			punit->iw = dispropty.vout_hd;
			punit->ih = dispropty.vout_vd;
		}
	}

	else if (punit->methord == FSM_PONG)
	{
		fsm_pong_t *pong = (fsm_pong_t *)punit->fsm;

		/* init */
		if (bestream)
		{
			pong->dx = pong->xs;
			pong->dy = pong->ys;

			/* rate change */
			if (pong->zor < pong->rmin) {
				pong->dr = pong->rs;
			}
			if (pong->zor > pong->rmax) {
				pong->dr = -pong->rs;
			}
			pong->zor = pong->zor + pong->dr;
		}

		punit->iw = (int)(punit->w * pong->zor);
		punit->ih = (int)(punit->h * pong->zor);

	}

	// calc output frame
	if (r90) {
		step = (uint16_t)((256L * (long)(dispropty.frame_w)) / (long)(punit->ih));
		dispropty.vout_hd = punit->ih;
		dispropty.vout_hr = step;

		step = (uint16_t)((256L * (long)(dispropty.frame_h)) / (long)(punit->iw));
		dispropty.vout_vd = punit->iw;
		dispropty.vout_vr = step;
	}
	else {
		step = (uint16_t)((256L * (long)(dispropty.frame_w)) / (long)(punit->iw));
		dispropty.vout_hd = punit->iw;
		dispropty.vout_hr = step;

		step = (uint16_t)((256L * (long)(dispropty.frame_h)) / (long)(punit->ih));
		dispropty.vout_vd = punit->ih;
		dispropty.vout_vr = step;
	}

	dispropty.flags |= (disp_propty_flags_vout_vd | disp_propty_flags_vout_vr);
	dispropty.flags |= (disp_propty_flags_vout_hd | disp_propty_flags_vout_hr);

	// calc start position
	if (punit->methord == FSM_FULL)
	{
		punit->x = (punit->w - punit->iw) / 2;
		punit->y = (punit->h - punit->ih) / 2;
	}

	else if (punit->methord == FSM_FXSZ)
	{
		if (r90) {
			punit->x = dispropty.vout_vs;
			punit->y = dispropty.vout_hs;
		}
		else {
			punit->x = dispropty.vout_hs;
			punit->y = dispropty.vout_vs;
		}
	}

	else if (punit->methord == FSM_PONG)
	{
		fsm_pong_t *pong = (fsm_pong_t *)punit->fsm;

		if (bestream)
		{
			punit->x = (punit->w - punit->iw) / 2;
			punit->y = (punit->h - punit->ih) / 2;
		}
		else
		{
			/* set */
			if (punit->x < pong->xs) {
				pong->dx = pong->xs;
			}
			if (punit->x + punit->iw > punit->w - pong->xs) {
				pong->dx = -pong->xs;
			}
			punit->x = punit->x + pong->dx;

			if (punit->y < pong->ys) {
				pong->dy = pong->ys;
			}
			if (punit->y + punit->ih > punit->h - pong->ys) {
				pong->dy = -pong->ys;
			}
			punit->y = punit->y + pong->dy;
		}
	}

	if (r90) {
		dispropty.vout_hs = punit->y;
		dispropty.vout_vs = punit->x;
	}
	else {
		dispropty.vout_hs = punit->x;
		dispropty.vout_vs = punit->y;
	}

	dispropty.flags |= (disp_propty_flags_vout_hs | disp_propty_flags_vout_vs);

	// done changes
	disp_property(&dispropty);
}

static int decode_stream_alloc(session_unit_t *punit)
{
	punit->img_addr = (uint8_t *)memalign(BASIC_PAGE_SIZE, MAX_JPEG_SIZE);
	if (punit->img_addr == NULL) {
		return -ENOMEM;
	}

	punit->dwj_addr = (uint8_t *)memalign(BASIC_PAGE_SIZE, MAX_BDWJ_SIZE);
	if (punit->dwj_addr == NULL) {
		free(punit->img_addr);
		return -ENOMEM;
	}
	punit->load_buf = punit->dwj_addr + MAX_REST_SIZE;

	punit->hpj = jpg2dwj_init(0);
	if (punit->hpj == NULL) {
		free(punit->dwj_addr);
		punit->dwj_addr = NULL;
		punit->load_buf = NULL;
		return -ENOEXEC;
	}

	punit->bufree = NULL;

	return 0;
}

static void decode_stream_free(session_unit_t *punit)
{
	if (punit->img_addr != NULL) {
		free(punit->img_addr);
		punit->img_addr = NULL;
	}

	if (punit->dwj_addr != NULL) {
		free(punit->dwj_addr);
		punit->dwj_addr = NULL;
	}
	punit->load_buf = NULL;

	if (punit->hpj != NULL) {
		jpg2dwj_deinit(punit->hpj);
		punit->hpj = NULL;
	}

	if (punit->bufree != NULL)
		sysbuf_free(punit->bufree);
	punit->bufree = NULL;

}
#endif

static void decode_stream_init(session_unit_t *punit)
{
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (punit == NULL)
		return;

	Uint32 pixformat = 0;

	if (SDL_Init(SDL_INIT_VIDEO)) {
		debug("Could not initialize SDL - %s\n", SDL_GetError());
		return;
	}
	// SDL 2.0 Support for multiple windows
	punit->screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED, punit->screen_w, punit->screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (!punit->screen) {
		debug("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return;
	}
	punit->sdlRenderer = SDL_CreateRenderer(punit->screen, -1, 0);

	// IYUV: Y + U + V  (3 planes)
	// YV12: Y + V + U  (3 planes)
	pixformat = SDL_PIXELFORMAT_IYUV;

	punit->sdlTexture = SDL_CreateTexture(punit->sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, punit->pixel_w, punit->pixel_h);
#else
	punit->x = 0;
	punit->y = 0;
	punit->w = dispropty.width;
	punit->h = dispropty.height;

	punit->load_size = 0;
	memset(&punit->view, 0, sizeof(punit->view));
	memset(&punit->jpeg, 0, sizeof(punit->jpeg));

	decode_fsm_start(punit);

#endif
}

static void decode_stream_deinit(session_unit_t *punit)
{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	decode_fsm_stop(punit);
#endif

	punit->load_size = 0;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	memset(&punit->view, 0, sizeof(punit->view));
	memset(&punit->jpeg, 0, sizeof(punit->jpeg));
#endif
}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
static int session_privateSSCMD_STREAM_STOP(struct SESSION *session); // error: static declaration of ‘session_privateSSCMD_STREAM_STOP’ follows non-static declaration
#endif
#ifdef MEDIA_AV_SYNC
static int get_mali_frame(struct SESSION *session);
static int avsync_display(struct SESSION *session)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	int video_passed_time = media_get_ms_diff(punit->sync->v_last_play_timestamp.last_tick);
	int audio_start_flag = 0;

	if (!punit->sync->a_cur_pkt_pts)
		audio_start_flag = 1;

#ifdef MEDIA_AV_SYNC_WITH_NUM
	int v_pts = ((punit->sync->v_cur_pkt_pts2 - punit->sync->v_last_decode_time * punit->sync->lace_total_num) * 1000)
				/ punit->sync->v_dts_time_base;
#else
	int v_pts = (punit->sync->v_cur_pkt_pts2 * 1000) / punit->sync->v_dts_time_base;
#endif
	int v_time_from_last = media_get_ms_diff(punit->sync->v_last_play_timestamp.last_tick);
	int a_pts = (punit->sync->a_cur_pkt_pts * 1000) / punit->sync->a_dts_time_base;
	int a_time_from_last = (audio_start_flag ? 0 : media_get_ms_diff(punit->sync->a_play_buf_flush_timestamp.last_tick));
	int a_buf_time = punit->sync->a_play_buf_remaining_time;
	int va_diff = v_pts + v_time_from_last - (a_pts + a_time_from_last - a_buf_time);

	punit->sync->va_diff = va_diff;

	static media_time_t print_timestamp = {0};
	int print_time = media_get_ms_diff(print_timestamp.last_tick);
	if (print_time > 5000) {
		print_timestamp.last_tick = xthal_get_ccount();
		printf("vpts: %d, apts: %d, ",v_pts,a_pts);
		printf("va_diff: %d\n",va_diff);
	}

	if (punit->sync->skip_flag) {
		video_passed_time = punit->sync->v_real_duration;
		va_diff = 0;
	}

	if (va_diff <= -20) {
		if (video_passed_time < punit->sync->v_real_duration / 2 - 10)
			return session->state;
	} else if (va_diff > -20 && va_diff <= 0) { // if video drop behind audio
		if (video_passed_time < punit->sync->v_real_duration - 20)
			return session->state;
	} else if ((va_diff > 0) && (va_diff <= 50)) { // don't care the diff
		if (video_passed_time < punit->sync->v_real_duration)
			return session->state;
	} else if (va_diff > 50 && va_diff <= 100) { // if video outstrip audio
		if (video_passed_time < punit->sync->v_real_duration + 20)
			return session->state;
	} else { // va_diff > 100
		if (video_passed_time < punit->sync->v_real_duration * 2)
			return session->state;
	}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (!punit->load_size) {
		get_mali_frame(session);
	} else {
		SDL_Rect sdlRect;
		f_read(&(punit->file), punit->buffer, punit->pixel_w * punit->pixel_h * punit->bpp / 8, NULL);
		punit->load_size -= punit->pixel_w * punit->pixel_h * punit->bpp / 8;
		SDL_UpdateTexture(punit->sdlTexture, NULL, punit->buffer, punit->pixel_w);
		// if window is resize
		sdlRect.x = 0;
		sdlRect.y = 0;
		sdlRect.w = punit->screen_w;
		sdlRect.h = punit->screen_h;
		SDL_RenderClear(punit->sdlRenderer);
		SDL_RenderCopy(punit->sdlRenderer, punit->sdlTexture, NULL, &sdlRect);
		SDL_RenderPresent(punit->sdlRenderer);
		usleep(40 * 1000);
		if (punit->load_size <= 0) {
			if (punit->load_buf)
				punit->load_buf->flags |= SYS_BUF_FLAG_EOF;
		}
	}
#else
	ret = get_mali_frame(session);
#endif

	return ret;
}
#endif /* MEDIA_AV_SYNC */

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	if (session->state != SSTATE_RUNNING
		&& session->state != SSTATE_RUNNING_IDLE)
	{

	}

#ifndef CONFIG_MEDIA_SAVE_DEMUX_FILES
#ifdef MEDIA_AV_SYNC
	avsync_display(session);
#endif
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	SDL_Event event;
	if (!SDL_PollEvent(&event))
		return session->state;

	if (event.type == (SDL_USEREVENT + 1)) { // #define REFRESH_EVENT (SDL_USEREVENT + 1)
	} else if (event.type == SDL_WINDOWEVENT) {
		//If Resize
		SDL_GetWindowSize(punit->screen, &punit->screen_w, &punit->screen_h);
	} else if (event.type == SDL_QUIT) {
		exit(1);
		session->state = SSTATE_STOP;
		SESSIONAPI(SSCMD_STREAM_STOP)(session);
	} else if (event.type == SDL_KEYDOWN) {
		//session->state = SSTATE_STOP;
		//exit(1);
	}

#endif
	return session->state;
}

static
int SESSIONAPI(SSCMD_DISPLAY_VIDEO_STREAM_CHANGED)(struct SESSION *session, void *info)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EPERM;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// switch frame
	decode_dbuf_switch(punit);

	// Erase background
	decode_fsm_erase_background(punit);

	// new setting

	decode_fsm_video_changed(punit, 1, info);
#endif

	return 0;
}

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static
void media_jpeg_dwj_show(session_unit_t *punit)
{
	int ret;

	if (punit->jpeg.isize <= 0
		|| punit->jpeg.isize >= MAX_JPEG_SIZE)
		return;

	memcpy(punit->img_addr, punit->dwj_addr, punit->jpeg.isize);
	dma_cache_wback_inv(punit->img_addr, punit->jpeg.isize);

	ret = jpeg_from_appram((long)punit->img_addr, &punit->jpeg);
	if (ret) {
		punit->jpeg.isize = 0;
		return;
	}

	punit->view.icon = 0;
	punit->view.Xs = 0;
	punit->view.Ys = 0;
	punit->view.Xj = 0;
	punit->view.Yj = 0;
	punit->view.Wv = punit->jpeg.width;
	punit->view.Hv = punit->jpeg.height;

	// show_jpeg_view_info(&punit->jpeg, &punit->view);

	switch (punit->streamtype)
	{
	case SST_JPEG:
	case SST_H264:
	case SST_HEVC:
	case SST_MPG2:
	case SST_MPG4:
	case SST_RV:
	case SST_VC1:
	case SST_VP8:
		return;
	default:;
	};

	lcdi_img(&punit->jpeg, &punit->view, 1);

}

static
void media_stream_jpeg_dwj(session_unit_t *punit, psysbuf_t buf)
{
	long rsize = MAX_BDWJ_SIZE - punit->load_size;
	if (rsize >= 0) {
		if (rsize > buf->size)
			rsize = buf->size;
		memcpy((void*)(punit->dwj_addr + punit->load_size),
			(void*)(buf->haddr + buf->offset), rsize);
		punit->load_size += rsize;
	}

	if (buf->flags & SYS_BUF_FLAG_EOF) {
#ifdef DO_CHECK_DWJ_MD5
		uint8_t md5k[MD5_DIGEST_SIZE];
		uint8_t *md5r = punit->dwj_addr + 48;
		memcpy(md5k, md5r, MD5_DIGEST_SIZE);
		memset(md5r, 0, MD5_DIGEST_SIZE);
		md5(punit->dwj_addr, punit->load_size, md5r);
		if (memcmp(md5k, md5r, MD5_DIGEST_SIZE)) {
			debug("\tError MD5 of DWJ jpeg file, ignore !\n");
		}
		else
#endif
		{
			punit->jpeg.isize = punit->load_size;
			media_jpeg_dwj_show(punit);
		}
		punit->load_size = 0;
	}

}

static
void media_stream_jpeg(session_unit_t *punit, psysbuf_t buf)
{
	long rsize = MAX_JPEG_SIZE - punit->load_size;
	if (rsize >= 0) {
		if (rsize > buf->size)
			rsize = buf->size;
		memcpy((void*)(punit->load_buf + punit->load_size),
			(void*)(buf->haddr + buf->offset), rsize);
		punit->load_size += rsize;
	}

	if (buf->flags & SYS_BUF_FLAG_EOF) {
		jpg2dwj_reset(punit->hpj, punit->dwj_addr);
		punit->jpeg.isize = jpg2dwj(punit->hpj, (void *)(punit->load_buf), punit->load_size);
		media_jpeg_dwj_show(punit);
		punit->load_size = 0;
	}

}
#endif

#ifdef MEDIA_AV_SYNC
static
int pts_flush(session_unit_t *punit, psysbuf_t buf)
{
	media_flush_time(&punit->sync->v_last_play_timestamp, &punit->sync->sys_time);
	if (buf->flags) {
#ifdef MEDIA_AV_SYNC_WITH_NUM
		if (punit->sync->skip_flag) {
			punit->sync->v_cur_pkt_num = buf->user.nHighPart;
			punit->sync->v_cur_pkt_pts2 = buf->user.nLowPart;
			punit->sync->skip_flag = 0;
		} else {
			punit->sync->v_cur_pkt_pts2 += punit->sync->v_last_decode_time;
		}
#else
		punit->sync->v_cur_pkt_num = buf->user.nHighPart;
		punit->sync->v_cur_pkt_pts2 = buf->user.nLowPart;
#endif
	}

	return 0;
}
#endif

static
psysbuf_t media_stream_frame(session_unit_t *punit, psysbuf_t buf)
{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// Erase background
	decode_fsm_erase_background(punit);
#endif

#ifdef MEDIA_AV_SYNC
	pts_flush(punit, buf);
#endif

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// frame changes
	decode_fsm_video_changed(punit, 0, buf);
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	SDL_Rect sdlRect;
	SDL_UpdateTexture(punit->sdlTexture, NULL, (unsigned char *)(buf->haddr + buf->offset), punit->pixel_w);
	// if window is resize
	sdlRect.x = 0;
	sdlRect.y = 0;
	sdlRect.w = punit->screen_w;
	sdlRect.h = punit->screen_h;
	SDL_RenderClear(punit->sdlRenderer);
	SDL_RenderCopy(punit->sdlRenderer, punit->sdlTexture, NULL, &sdlRect);
	SDL_RenderPresent(punit->sdlRenderer);
#endif
	// Free pre-frame and save current
	if (punit->bufree != NULL)
		sysbuf_free(punit->bufree);
	punit->bufree = buf;

	return NULL;
}

static
#ifdef MEDIA_AV_SYNC
int get_mali_frame(struct SESSION *session)
#else
int SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(struct SESSION *session, session_buffer_t *sb)
#endif
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EPERM;

	psysbuf_t buf = SessionBufferPop(session);
	if (buf == NULL)
		return 0;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// double buffer
	decode_dbuf_switch(punit);
	// calc frame
	punit->framecounter++;
#endif

	// run action
	switch (buf->flags & SYSBUF_FLAG_TYPE_MASK)
	{
	case SST_LCDI_JPEG:
	{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		media_stream_jpeg(punit, buf);
#endif
	}break;
	case SST_LCDI_JPEG_DWJ:
	{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		media_stream_jpeg_dwj(punit, buf);
#endif
	}break;
	case SST_JPEG:
	case SST_H264:
	case SST_HEVC:
	case SST_MPG2:
	case SST_MPG4:
	case SST_RV:
	case SST_VC1:
	case SST_VP8:
	{
		buf = media_stream_frame(punit, buf);
	}break;
	};

	if (buf != NULL)
		sysbuf_free(buf);

	return 0;
}

#ifdef MEDIA_AV_SYNC
static
int SESSIONAPI(SSCMD_STREAM_SKIP)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	// reinit SessionBuffer
	SessionBufferDeInit(session);

	return 0;
}
#endif

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_INITED)
		return -EPERM;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	punit->screen_w = punit->pixel_w = file->tv.width;
	punit->screen_h = punit->pixel_h = file->tv.height;

	punit->load_size = 0;
	punit->load_offset = 0;
	punit->eof = 0;
	punit->bpp = 12;

	if (file->filename) {
		if (f_open(&(punit->file), file->filename, FA_READ) != FR_OK) {
			debug("open stream file error \n");
			return -EBADF;
		} else {
			punit->load_size = f_size(&(punit->file));
			debug("stream file size is %ld \n", punit->load_size);
		}
	}
#endif

	SessionBufferDeInit(session);

	decode_stream_init(punit);

	punit->streamtype = file->type;

#ifdef MEDIA_AV_SYNC
	punit->sync = file->sync;
#endif

	session->state = SSTATE_RUNNING;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_RUNNING)
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;

	decode_stream_deinit(punit);

	SessionBufferDeInit(session);

	punit->streamtype = SST_NONE;

	// set state
	session->state = SSTATE_INITED;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (punit->load_buf)
		sysbuf_free(punit->load_buf);
	punit->load_buf = NULL;

	if (punit->file)
		f_close(&punit->file);

	SDL_Quit();
#endif

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (session == NULL || punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
		if (session->state == SSTATE_RUNNING
			|| session->state == SSTATE_RUNNING_IDLE)
			session->state = SSTATE_RUNNING_PAUSE;
	}break;
	case SSCMD_STREAM_RESUME:
	{
		debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
		if (session->state == SSTATE_RUNNING_PAUSE)
			session->state = SSTATE_RUNNING;
	}break;
	case SSCMD_STREAM_STOP:
	{
		debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_DISPLAY_LCDI:
	{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		uint8_t *db = (uint8_t *)params;
		debug("Session '%s' command  'SSCMD_DISPLAY_LCDI:%d'\n", session->name, (int)db[0]);
		ret = lcdi_cmd(db, 64, 0);
#endif
	}break;
	case SSCMD_DISPLAY_VIDEO_STREAM_CHANGED:
	{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		return SESSIONAPI(SSCMD_DISPLAY_VIDEO_STREAM_CHANGED)(session, (video_stream_info_t *)params);
#endif
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
#ifdef MEDIA_AV_SYNC
		// insteed SESSIONAPI(SSCMD_STREAM_BUFCHANGED) with get_mali_frame() at roll poling run
#else
		return SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(session, (session_buffer_t*)params);
#endif
	}break;
	case SSCMD_STREAM_SKIP:
	{
#ifdef MEDIA_AV_SYNC
		return SESSIONAPI(SSCMD_STREAM_SKIP)(session);
#endif
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit != NULL) {
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		decode_fsm_free(punit);
		decode_stream_free(punit);
#endif
		free(punit);
	}

	debug("SessionDeInit : %s ..\n", session->name);

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	lcdi_exit();
#endif

	session->handle = NULL;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	int res;
	session_unit_t *punit = (session_unit_t*)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));

	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

	SessionBufferInit(session);

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	res = decode_stream_alloc(punit);
	if (res)
		return NULL;

	res = decode_fsm_alloc(punit, FSM_FXSZ);
	if (res) {
		decode_stream_free(punit);
		return NULL;
	}

	// internal initial
	decode_fsm_init(punit);
#endif

	session->state = SSTATE_RUNNING;

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_display =
{ "display", (1 << SST_DISPLAY),
&SESSIONAPI(SessionInit),
};
